﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using HK.Core.Common;
using UnityEngine;
using UnityEngine.Timeline;
using UnityEngine.Video;
using UnityEngine.U2D;
using HLogger = HK.Core.Logs.Logger;

namespace HK.Core.Utils
{
    /// <summary>
    /// 资产文件类型.
    /// </summary>
    public enum AssetFileType
    {
        /// <summary>
        /// 未知.
        /// </summary>
        Unknown = 0,
        /// <summary>
        /// Asset Bundle
        /// </summary>
        AssetBundle = 1,
        /// <summary>
        /// Asset Bundle Manifest
        /// </summary>
        Manifest = 2,
        /// <summary>
        /// 预制体.
        /// </summary>
        Prefab = 100010,
        /// <summary>
        /// 音效.
        /// </summary>
        Audio = 100020,
        /// <summary>
        /// 视频文件
        /// </summary>
        Video = 100030,
        /// <summary>
        /// 动画文件
        /// </summary>
        Animation = 100040,
        /// <summary>
        /// 动画文件Controller
        /// </summary>
        AnimationController = 100041,
		/// <summary>
		/// IK动作文件
		/// </summary>
		IkMask = 100042,
        /// <summary>
        /// 材质.
        /// </summary>
        Mat = 100050,
        /// <summary>
        /// 精灵.
        /// </summary>
        Sprite = 100060,
        /// <summary>
        /// 纹理.
        /// </summary>
        Texture = 100070,
        /// <summary>
        /// 图集.
        /// </summary>
        Atlas = 100080,
        /// <summary>
        /// 字体
        /// </summary>
        Font = 100090,
        /// <summary>
        /// 网格数据
        /// </summary>
        Mesh = 100100,
        /// <summary>
        /// 序列化文件(.asset)文件.
        /// </summary>
        SAsset = 100110,
        /// <summary>
        /// 着色器文件
        /// </summary>
        Shader = 100120,
        /// <summary>
        /// (.txt)文件.
        /// </summary>
        Txt = 100130,
        /// <summary>
        /// Lua文件
        /// </summary>
        Lua = 100131,
        /// <summary>
        /// (.json)文件.
        /// </summary>
        Json = 100132,
        /// <summary>
        /// 二进制文件
        /// </summary>
        Binary = 100133,
        /// <summary>
        /// Timeline之Signal文件
        /// </summary>
        TimelineSignal = 1001400,
        /// <summary>
        /// TimeLine之动画文件(*.playable)
        /// </summary>
        PlayableAsset = 1001401,
        /// <summary>
        /// 场景.
        /// </summary>
        Scene = 100990,
        /// <summary>
        /// C# 程序文件
        /// </summary>
        CsSharp = 200000,
        /// <summary>
        /// DLL库文件
        /// </summary>
        Dll = 200100,
        /// <summary>
        /// Bundles
        /// </summary>
        Bundle = 2001001,
    }
    
    /// <summary>
    /// 工具类 : IO
    /// <para>* 文件、目录等IO对象相关处理。</para>
    /// </summary>
    public static class UtilsIo
    {
	    private static bool _initialized = false;
	    private static readonly string AppRootDir = null;
	    static UtilsIo()
	    {
		    AppRootDir = Application.dataPath;
	    }

	    /// <summary>
	    /// 初始化
	    /// </summary>
	    public static void Init()
	    {
		    if(_initialized) return;
		    HLogger.LInfo($"UtilsIo::Init():AppRootDir:{AppRootDir}");
		    _initialized = true;
	    }

#region Md5

	    /// <summary>
	    /// 取得文件的Md5码.<BR/>
	    /// * 线程安全
	    /// </summary>
	    /// <param name="iFilePath">文件路径.</param>
	    /// <param name="oFileSize">文件大小.</param>
	    /// <returns>文件的Md5码.</returns>
	    public static string GenerateMd5OfFile(string iFilePath, out long oFileSize)
	    {
		    oFileSize = 0L;
		    if (!File.Exists(iFilePath))
		    {
			    HLogger.Error($"UtilsIo::GenerateMd5OfFile():File not exist!(Path:{iFilePath})");
			    return null;
		    }
			
		    string md5 = null;
		    var strMd5Buffer = new StringBuilder();

		    using (var fs = new FileStream(iFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
		    {
			    oFileSize = fs.Length;
			    var bytes = new byte[oFileSize];
			    var read = fs.Read(bytes, 0, (int)oFileSize);
			    if (read == (int)oFileSize)
			    {
				    byte[] result = null;
				    using (var md5Provider = new MD5CryptoServiceProvider())
				    {
					    result = md5Provider.ComputeHash(bytes);
				    }
				    var count = result?.Length ?? 0;
				    if (0 < count)
				    {
					    for (var idx = 0; idx < count; ++idx)
					    {
						    var charByte = result[idx];
						    strMd5Buffer.Append(charByte.ToString("X"));
					    }
					    md5 = strMd5Buffer.ToString().ToLower();
				    }
				    HLogger.Info($"UtilsIo::GenerateMd5OfFile():Path:{iFilePath} Len:{oFileSize}\n  Md5:{md5}");
			    }
			    else
			    {
				    HLogger.Error($"UtilsIo::GenerateMd5OfFile():Failed!(Path:{iFilePath} Len:{oFileSize}/Read:{read})");
			    }
		    }
            
		    return md5;
	    }

#endregion

#region Crc

		/// <summary>
		///	生成Crc码
		/// <para>小于200字节文件以4字节为单位，全文XOR再与0x1做XOR，不足4字节部分优先作为ushort执行XOR，最后还有单个字节剩余作为ubyte执行XOR</para>
		/// <para>* 大于200字节文件只对最初100和最后100字节做上述校验</para>
		/// <para>* 此算法比起MD5速度更快</para>
		/// <para>* 由于在230804后大部分热更新文件是AB包，以及zlib压缩的文本文件，只需要确认文件本身是否被替换或CDN缓存，不需要确认文件全文完整，</para>
		/// <para>* 不完整的文件多数因为最后若干字节是0被此算法发现，或者无法解压，并在后续步骤发现错误</para>
		/// </summary>
		/// <param name="iInput">输入字节数组</param>
		/// <returns>CRC码</returns>
		public static uint GenerateCRC(byte[] iInput)
		{
			uint crcCode = 0;
			using (var stream = new MemoryStream(iInput))
			{
				crcCode = GenerateCRC(stream);
			}
			return crcCode;
		}

		/// <summary>
		/// 生成Crc码
		/// </summary>
		/// <param name="iFilePath">文件路径</param>
	    /// <param name="oFileSize">文件大小.</param>
		/// <returns>CRC码</returns>
		public static uint GenerateCRCByFile(string iFilePath, out long oFileSize)
		{
			uint crcCode = 0;
			oFileSize = 0L;
			using (var fs = new FileStream(iFilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
			{
				oFileSize = fs.Length;
				crcCode = GenerateCRC(fs);
			}
			return crcCode;
		}

		/// <summary>
		/// 生成Crc码
		/// <para>此接口可直接读FileStream，避免将文件整个加载进内存</para>
		/// <para>* 小于200字节文件以4字节为单位，全文XOR再与0x1做XOR，不足4字节部分优先作为ushort执行XOR，最后还有单个字节剩余作为ubyte执行XOR</para>
		/// <para>* 大于200字节文件只对最初100和最后100字节做上述校验</para>
		/// <para>* 此算法比起MD5速度很快，</para>
		/// <para>* 由于在230804后大部分热更新文件是AB包，以及zlib压缩的文本文件，只需要确认文件本身是否被替换或CDN缓存，不需要确认文件全文完整，</para>
		/// <para>* 不完整的文件多数因为最后若干字节是0被此算法发现，或者无法解压，并在后续步骤发现错误</para>
		/// </summary>
		/// <param name="iStream">输入流</param>
		/// <returns>Crc码</returns>
		public static uint GenerateCRC(Stream iStream)
		{
			uint crc = 0;
			using (var reader = new BinaryReader(iStream))
			{
				var len = iStream.Length;
				if (len < 200) {
					while (len - iStream.Position >= 4) {
						crc ^= reader.ReadUInt32();
					}
					if (len - iStream.Position >= 2) 
					{
						crc ^= reader.ReadUInt16();
					}
					if (len - iStream.Position >= 1) 
					{
						crc ^= reader.ReadByte();
					}
				} else {
					for (var i = 0; i < 25; i++) 
					{
						crc ^= reader.ReadUInt32();
					}
					iStream.Position = len - 100;
					for (var i = 0; i < 25; i++) 
					{
						crc ^= reader.ReadUInt32();
					}
				}
				crc ^= 1;//免空文件用crc==0校验空内容成立
				if(crc == 0) {
					crc = 1;
				}
			}
			return crc;
		}

#endregion

#region Check

        /// <summary>
        /// 判断是否为场景文件
        /// </summary>
        /// <param name="iFilePath">文件路径</param>
        /// <returns>true:是; false:不是;</returns>
        public static bool IsSceneFile(string iFilePath)
        {
            // 路径为空，则无需更名
            if (string.IsNullOrEmpty(iFilePath)) return false;
            if (iFilePath.Contains("/.svn/") || iFilePath.Contains("\\.svn\\")) return false;
			        
            // 文件不存在，则无需更名
            if (!File.Exists(iFilePath)) return false;
			        
            // 取得文件类型
            var assetFileType = GetAssetFileTypeByAssetKey(iFilePath);
            return AssetFileType.Scene == assetFileType;
        }
        
        /// <summary>
        /// 判断是否为Lua文件
        /// </summary>
        /// <param name="iFilePath">文件路径</param>
        /// <param name="iFileExistCheck">文件路径</param>
        /// <returns>true:是; false:不是;</returns>
        public static bool IsLuaFile(string iFilePath, bool iFileExistCheck)
        {
            // 路径为空，则无需更名
            if (string.IsNullOrEmpty(iFilePath)) return false;
            if (iFilePath.Contains("/.svn/") || iFilePath.Contains("\\.svn\\")) return false;
			
            // 文件不存在，则无需更名
            if (iFileExistCheck && !File.Exists(iFilePath)) return false;
	        
            // 取得文件类型
            var assetFileType = GetAssetFileTypeByAssetKey(iFilePath);
            return AssetFileType.Lua == assetFileType;
        }

        /// <summary>
        /// 判断是否为纹理文件
        /// </summary>
        /// <param name="iFilePath">路径</param>
        /// <returns>true:是; false:不是;</returns>
        public static bool IsTextureFile(string iFilePath)
        {
            // 路径为空，则无需更名
            if (string.IsNullOrEmpty(iFilePath)) return false;
            if (iFilePath.Contains("/.svn/") || iFilePath.Contains("\\.svn\\")) return false;
			
            // 文件不存在，则无需更名
            if (!File.Exists(iFilePath)) return false;
	        
            // 取得文件类型
            var assetFileType = GetAssetFileTypeByAssetKey(iFilePath);
            return AssetFileType.Texture == assetFileType;
        }

        /// <summary>
        /// 判断是否为库文件
        /// </summary>
        /// <param name="iFilePath">文件Path</param>
        /// <returns>true:是; false:不是;</returns>
        public static bool IsBundleFile(string iFilePath)
        {
            if (string.IsNullOrEmpty(iFilePath)) return false;
            if (iFilePath.Contains("/.svn/") || iFilePath.Contains("\\.svn\\")) return false;
            
            // 取得文件类型
            var assetFileType = GetAssetFileTypeByAssetKey(iFilePath);
            return AssetFileType.Bundle == assetFileType;
        }

        /// <summary>
        /// 判断是否为库文件
        /// </summary>
        /// <param name="iFilePath">文件Path</param>
        /// <returns>true:是; false:不是;</returns>
        public static bool IsDllFile(string iFilePath)
        {
            if (string.IsNullOrEmpty(iFilePath)) return false;
            if (iFilePath.Contains("/.svn/") || iFilePath.Contains("\\.svn\\")) return false;
            
            var fileExtension = Path.GetExtension(iFilePath).ToLower();
            if (string.IsNullOrEmpty(fileExtension)) return false;

            // 取得文件类型
            var assetFileType = GetAssetFileTypeByAssetKey(iFilePath);
            return AssetFileType.Dll == assetFileType;
        }		
        
        /// <summary>
        /// 判断是不是Resource路径
        /// <para>* 支持得格式如下</para>
        /// <para>  -> Resources/XXX</para>
        /// </summary>
        /// <param name="iPath">路径</param>
        /// <returns>true:是; false:否;</returns>
        public static bool IsResourcePath(string iPath)
        {
            var path = iPath;
            if (string.IsNullOrEmpty(path)) return false;
            // 编辑器下的路径
            return !path.Contains("Editor") && path.Contains(Const.PATH_RESOURCES_PREFIX);
        }

        /// <summary>
        /// 判断是否为资源文件
        /// </summary>
        /// <param name="iAssetKey">AssetKey</param>
        /// <returns>true:资源文件; false:非资源文件;</returns>
        public static bool IsResourceFile(string iAssetKey, out AssetFileType oAssetType)
        {
            oAssetType = AssetFileType.Unknown;
            if (string.IsNullOrEmpty(iAssetKey)) return false;

            oAssetType = GetAssetFileTypeByAssetKey(iAssetKey, out var oFileExtension);
            return IsResourceFile(oAssetType);
        }

        /// <summary>
        /// 判断是否为资源文件
        /// </summary>
        /// <param name="iAssetFile">资产文件类型</param>
        /// <returns>true:资源文件; false:非资源文件;</returns>
        public static bool IsResourceFile(AssetFileType iAssetFile)
        {
            return AssetFileType.AssetBundle != iAssetFile && AssetFileType.Manifest != iAssetFile && 
                   AssetFileType.Lua != iAssetFile && AssetFileType.CsSharp != iAssetFile && 
                   AssetFileType.Dll != iAssetFile && AssetFileType.Bundle != iAssetFile;
        }

#endregion

#region Convert

		/// <summary>
		/// 验证路径
		/// <para>* 路径相关验证以及处理</para>
		/// </summary>
		/// <param name="iPath">路径</param>
		/// <returns>验证后的路径</returns>
		private static string VerifyPath(string iPath)
		{
			var path = iPath;
			if (string.IsNullOrEmpty(path)) return path;

			path = path.Replace("\\", "/");
					
			if (!string.IsNullOrEmpty(path) &&
			    (path.Contains("/..") || path.Contains("../") || path.Contains("..")))
			{
				var fileInfo = new FileInfo(path);
				path = fileInfo.FullName;
			}
			return path;
		}
		
		/// <summary>
		/// 验证目录
		/// <para>* 目录相关验证以及处理</para>
		/// </summary>
		/// <param name="iDir">目录</param>
		/// <returns>验证后的目录</returns>
		public static string VerifyDir(string iDir)
		{
			var dir = iDir;
			if (string.IsNullOrEmpty(dir)) return dir;

#if UNITY_IOS || UNITY_IPHONE || UNITY_STANDALONE_OSX
			dir = dir.Replace("\\", $"{Path.DirectorySeparatorChar}");
			if (!string.IsNullOrEmpty(dir) &&
			    (dir.Contains("/..") || dir.Contains("../") || dir.Contains("..")))
			{
				var dirInfo = new DirectoryInfo(dir);
				dir = dirInfo.FullName;
			}
#else
			dir = dir.Replace("/", "\\");
			if (!string.IsNullOrEmpty(dir) &&
			    (dir.Contains("\\..") || dir.Contains("..\\") || dir.Contains("..")))
			{
				var dirInfo = new DirectoryInfo(dir);
				dir = dirInfo.FullName;
			}
#endif
			return dir;
		}
        
		/// <summary>
		/// Asset路径判断
		/// </summary>
		/// <param name="iPath">路径</param>
		/// <param name="oAssetPath">Asset路径(转换后的路径)</param>
		/// <param name="iIgnoreCase">忽略大小写</param>
		/// <param name="iStrict">严格标志位(true:不在当前工程下的目录，会返回为null; false:不在当前工程下目录，会直接返回;)</param>
		/// <returns>true:是; false:否;</returns>
		public static bool IsAssetPath(string iPath, out string oAssetPath, 
			bool iIgnoreCase = false, bool iStrict = false)
		{
			oAssetPath = iPath;
			if (string.IsNullOrEmpty(iPath)) return false;
			var path = iPath;

			// 统一路径分隔符号
			path = path.Replace(Path.DirectorySeparatorChar, '/');

			var prefix = iIgnoreCase ? Const.PATH_ASSET_PREFIX.ToLower() : Const.PATH_ASSET_PREFIX;
			var flg = iIgnoreCase ? path.ToLower().StartsWith(prefix) : path.StartsWith(prefix);
			if (!flg)
			{
				flg = iIgnoreCase ? path.ToLower().StartsWith(AppRootDir.ToLower()) : path.StartsWith(AppRootDir);
				if (!flg)
				{
					if (iStrict)
					{
						oAssetPath = null;
					}
					return false;
				}
			}
			
			// 转换一次路径
			oAssetPath = ConvertToAssetPath(path, iIgnoreCase, iStrict);

			// 忽略大小写
			if (iIgnoreCase)
			{
				return !string.IsNullOrEmpty(oAssetPath) && oAssetPath.ToLower().StartsWith(prefix);
			}
			return !string.IsNullOrEmpty(oAssetPath) && oAssetPath.StartsWith(prefix);
		}

		/// <summary>
		/// 将指定路径转换成相应的Asset路径<BR/>
		/// 例如：<BR/>
		/// * Window<BR/>
		/// 	C:\WorkDir\HFramework\Assets\Scenes\SampleScene.unity -> Assets\Scenes\SampleScene.unity<BR/>
		/// * Mac OS<BR/>
		/// 	/Users/DeskTop/WorkDir/HFramework/Assets/Scenes/SampleScene.unity -> Assets/Scenes/SampleScene.unity
		/// </summary>
		/// <param name="iPath">路径</param>
		/// <param name="iIgnoreCase">忽略大小写</param>
		/// <param name="iStrict">严格标志位(true:不在当前工程下的目录，会返回为null; false:不在当前工程下目录，会直接返回;)</param>
		/// <returns>路径</returns>
		public static string ConvertToAssetPath(string iPath, bool iIgnoreCase = false, bool iStrict = false)
		{
			var path = iPath;
			if (string.IsNullOrEmpty(path) || 0 >= path.Length)
			{
				return null;
			}

			// 替换反斜杠
			path = VerifyPath(path);

			var index = -1;
			var pathPrefix = iIgnoreCase ? Const.PATH_ASSET_PREFIX.ToLower() : Const.PATH_ASSET_PREFIX;

			// 忽略大小写
			if (iIgnoreCase)
			{
				if (path.ToLower().StartsWith(pathPrefix))
				{
					return path;
				}
				
				index = path.ToLower().IndexOf(AppRootDir.ToLower(), StringComparison.Ordinal);
				if (-1 != index)
				{
					path = path.Substring(index + AppRootDir.Length, path.Length - AppRootDir.Length - index);
				}
				else
				{
					return iStrict ? null : path;
				}
					
				if (path.StartsWith("/"))
				{
					path = path.Substring(1);
				}

				if (!path.StartsWith(pathPrefix))
				{
					path = $"{Const.PATH_ASSET_PREFIX}{path}";
				}
				
				return iStrict ? null : path;
			}
			
			if (path.StartsWith(pathPrefix))
			{
				return path;
			}
			
			index = path.ToLower().IndexOf(AppRootDir.ToLower(), StringComparison.Ordinal);
			if (-1 != index)
			{
				path = path.Substring(index + AppRootDir.Length, path.Length - AppRootDir.Length - index);
			}
			else
			{
				return iStrict ? null : path;
			}
			
			if (path.StartsWith("/"))
			{
				path = path.Substring(1);
			}

			if (!path.StartsWith(pathPrefix))
			{
				path = $"{Const.PATH_ASSET_PREFIX}{path}";
			}

			return path;
		}
        
		/// <summary>
		/// 转换路径
		/// </summary>
		/// <param name="iPath">路径</param>
		/// <param name="oResPath">路径(转化后的路径)</param>
		/// <returns>Resource的路径</returns>
		public static bool ConvertToResourcesPath(string iPath, out string oResPath)
		{
			oResPath = iPath;
			if (string.IsNullOrEmpty(iPath) || iPath.Contains("Editor")) return false;
			var path = ConvertToAssetPath(iPath);
			if (!path.Contains(Const.PATH_RESOURCES_PREFIX))
			{
				//Loger.Warning($"UtilsIo::ConvertToResourcesPath():Path invalid!!(Path:{iPath})");
				return false;
			}
			var index = path.IndexOf(Const.PATH_RESOURCES_PREFIX, StringComparison.Ordinal);
			if (-1 == index)
			{
				//Loger.Warning($"UtilsIo::ConvertToResourcesPath():Path invalid!!(Path:{iPath})");
				return false;
			}
			oResPath = path.Substring(index+Const.PATH_RESOURCES_PREFIX.Length);
			
			// 去除文件后缀
			var lastIndex = oResPath.LastIndexOf(".", StringComparison.Ordinal);
			if (-1 != lastIndex)
			{
				oResPath = oResPath.Substring(0, lastIndex);
			}
			return true;
		}

#endregion

#region Directory

        /// <summary>
        /// 校验路径，若无则创建目录.
        /// </summary>
        /// <returns><c>true</c>, OK, <c>false</c> NG.</returns>
        /// <param name="iDir">目录.</param>
        public static bool CheckAndCreateDirByFullDir(string iDir)
        {
            if (string.IsNullOrEmpty(iDir))
            {
	            HLogger.Error("UtilsIo::CheckAndCreateDirByFullDir():The dir is null or empty!");
                return false;
            }
			        
            // 若是Apk中的只读路径
            if (iDir.StartsWith("jar:file://") || iDir.Contains("/base.apk!/") || iDir.Contains("/base.apk/"))
            {
	            HLogger.Warning($"UtilsIo::CheckAndCreateDirByFullDir():Make sure the dir is exit!(Dir:{iDir})");
                return true;
            }
			        
            // 若已经存在则返回
            if (CheckAndCreateDir (iDir)) {
                return true;
            }
			        
            // 初始化目录信息
            var dirs = new Stack<string>();
            var parent = new DirectoryInfo (iDir);
            while (null != parent) {
                dirs.Push (parent.Name);
                parent = parent.Parent;
            }
            var isOk = true;
            var dir = "";
#if UNITY_EDITOR_OSX
            dir = Path.DirectorySeparatorChar.ToString();
#endif
            var index = 0;
            while (0 < dirs.Count) {
                if(0 >= index) {
                    dir = dirs.Pop ();
                } else if(1 == index) {
                    dir = $"{dir}{dirs.Pop()}";
                } else {
                    dir = $"{dir}{Path.DirectorySeparatorChar}{dirs.Pop()}";
                }
                if (false == CheckAndCreateDir (dir)) {
                    isOk = false;
                    break;
                }
                index++;
            }
            return isOk;
        }        

        private static bool CheckAndCreateDir(string iDir) {
            if (Directory.Exists(iDir)) return true;

            var flg = true;
#if UNITY_EDITOR
            try
            {
#endif
                Directory.CreateDirectory (iDir);
#if UNITY_EDITOR
            }
            catch (Exception exp)
            {
	            HLogger.Fatal($"UtilsIo():CheckAndCreateDir():{exp.GetType().Name} - {exp.Message} \n {exp.StackTrace}");
                flg = false;
            }
#endif
			
            return flg && Directory.Exists(iDir);
        }

        /// <summary>
        /// 取得文件所在目录
        /// </summary>
        /// <param name="iFilePath">文件路径</param>
        /// <param name="iExistCheck">存在校验(true:若目标目录不存在，则自动生成该目录;)</param>
        /// <param name="iDirSeparatorChar">目录分隔符</param>
        /// <returns>文件所在目录</returns>
        public static string GetFileDirByPath(string iFilePath, bool iExistCheck = false, string iDirSeparatorChar = "/")
        {
	        if (string.IsNullOrEmpty(iFilePath)) return null;

	        // 若本身就是目录路径，则原样返回
	        if (Directory.Exists(iFilePath)) return iFilePath;

	        var lastIndex = iFilePath.LastIndexOf(iDirSeparatorChar, StringComparison.Ordinal);
	        if (-1 == lastIndex) return iFilePath;
	        
	        var dstDir = iFilePath.Substring(0, lastIndex);
	        if (iExistCheck)
	        {
		        if (!CheckAndCreateDirByFullDir(dstDir))
		        {
			        HLogger.Error($"UtilsIo():GetFileDirByPath():Failed!(Dir:{dstDir})");
			        return dstDir;
		        }
	        }
	        
	        return dstDir;
        }
        
        /// <summary>
        /// Ab打包文件过滤器委托定义
        /// </summary>
        /// <param name="iDir">文件路径</param>
        /// <param name="oAssetDir">文件目录(有效路径)</param>
        public delegate bool DirFilter(string iDir, out string oAssetDir);
        
        /// <summary>
        /// 默认目录过滤器
        /// </summary>
        /// <param name="iDir">文件目录</param>
        /// <param name="oAssetDir">文件目录(有效路径)</param>
        /// <returns>true:有效; false:无效;</returns>
        private static bool DefaultDirFilter(string iDir, out string oAssetDir)
        {
	        oAssetDir = iDir;
	        if (string.IsNullOrEmpty(iDir)) return false;
	        
	        // 统一转成小写
	        var dir = iDir.ToLower();
	        
	        // .git
	        if (dir.Contains("/.git")) return false;

	        // .idea
	        if (dir.Contains("/.idea")) return false;
	        
	        // .svn
	        if (dir.Contains("/.svn")) return false;

	        // .vs
	        if (dir.Contains("/.vs")) return false;

	        // .vscode
	        if (dir.Contains("/.vscode")) return false;

	        if (!IsAssetPath(iDir, out oAssetDir)) return false;
	        return !string.IsNullOrEmpty(oAssetDir);
        }
        
        /// <summary>
        /// 取得所有子目录列表
        /// </summary>
        /// <param name="iDir">目标目录</param>
        /// <param name="iDirFilter">目标目录过滤器</param>
        /// <param name="iIsTopDirOnly">顶层目录标志位</param>
        /// <param name="iIncludeSelf">包含自身标志位</param>
        /// <returns>打包用的所有子文件夹列表</returns>
        public static List<string> GetAllDirectories(string iDir, DirFilter iDirFilter = null, 
	        bool iIsTopDirOnly = true, bool iIncludeSelf = false)
        {
	        if (string.IsNullOrEmpty(iDir)) return null;
	        
	        var dirs = new List<string>();

	        try
	        {
		        if (Directory.Exists(iDir))
		        {
			        var filter = iDirFilter ?? DefaultDirFilter;
			        var childDirs = Directory.GetDirectories (iDir);
			        if (0 < childDirs.Length)
			        {
				        var count = childDirs.Length;
				        for (var idx = 0; idx < count; ++idx)
				        {
					        var childDir = childDirs[idx];
					        if(!Directory.Exists(childDir)) continue;
							
					        if(!filter(childDir, out var oChildDir)) continue;
					        // 不包含自身
					        if (!iIncludeSelf && childDir.Equals(iDir))
					        {
						        continue;
					        }
					        dirs.Add(oChildDir);

					        if (!iIsTopDirOnly)
					        {
						        var dirsTmp = GetAllDirectories(oChildDir, filter, false, true);
						        if (0 < dirsTmp.Count)
						        {
							        dirs.AddRange(dirsTmp);
						        }
					        }
				        }
			        }
		        }
	        }
	        catch (DirectoryNotFoundException exp)   
	        {  
		        HLogger.Fatal($"GetAllChildDirectoriesForAbBuild() Failed!(dir:{iDir} detail:{exp.Message})");
	        } 
			
	        return dirs;
        }

        /// <summary>
        /// 取得目录名
        /// </summary>
        /// <param name="iDir">目录</param>
        /// <param name="oParentDir">父级目录</param>
        /// <param name="iExistCheck">存在校验</param>
        /// <returns>目录名</returns>
        public static string GetDirName(string iDir, out string oParentDir, bool iExistCheck = false)
        {
	        oParentDir = null;
	        var selfDir = iDir;
	        if (string.IsNullOrEmpty(selfDir)) return null;
	        selfDir = selfDir.Replace("\\", "/");
	        
	        if (iExistCheck && !Directory.Exists(selfDir))
	        {
		        return null;
	        }

	        var lastIndex = selfDir.LastIndexOf("/", StringComparison.Ordinal);
	        if (-1 == lastIndex) return selfDir;

	        oParentDir = selfDir.Substring(0, lastIndex);
	        return selfDir.Substring(lastIndex + 1);
        }

#endregion

#region File

		/// <summary>
		/// 文件浏览面板 - 文件过滤器
		/// <para>* 格式 : 下列设定必须时 描述 + 文件后缀 这样成对设定</para>
		/// <para>* 文件后缀名 : 多个同时指定时，用逗号分隔。如:jpg,png</para>
		/// </summary>
		public static readonly string[] FileDefaultExtensions = new[]
		{
			"All Files", "*",    // TODO:iOS下无效
			"Plist Files", "plist", 
			"Image files", "png,jpg,jpeg", 
			"Json/Txt/Xml/Plist files", "txt,bytes,asset,json,xml,plist,strings",
			"CSharp&C", "cs,c,cpp,m,mm,h,hpp",
			"Lib&Framework", "dll,so,aar",
			"Bundles", "bundle"
		};

        /// <summary>
        /// 文本文件后缀
        /// </summary>
        public static readonly string TEXT_FILE_EXT = ".txt";
                
        /// <summary>
        /// 二进制文件后缀(.byte)
        /// </summary>
        public static readonly string BINARY_FILE_EXT_0 = ".byte";
                
        /// <summary>
        /// 二进制文件后缀(.bytes)
        /// </summary>
        public static readonly string BINARY_FILE_EXT_1 = ".bytes";

        /// <summary>
        /// 二进制文件后缀(.bin)
        /// </summary>
        public static readonly string BINARY_FILE_EXT_2 = ".bin";
                
        /// <summary>
        /// 图集文件后缀
        /// </summary>
        public static readonly string SPRITE_ATLAS_EXT = ".spriteatlas";

        /// <summary>
        /// 默认Ab文件后缀
        /// </summary>
        public static readonly string AB_DEFAUAT_EXT = ".unity3d";

        /// <summary>
        /// 场景文件后缀
        /// </summary>
        public static readonly string SCENE_EXT = ".unity";
        
                /// <summary>
        /// Asset文件后缀列表<BR/>
        /// * 禁止统一后缀名属于不同类型
        /// </summary>
        private static Dictionary<AssetFileType, List<string>> _assetFileExtensions = new Dictionary<AssetFileType, List<string>>
        {
            // { AbType.None, new List<string> {} },
            { AssetFileType.AssetBundle, new List<string> { AB_DEFAUAT_EXT } },
            { AssetFileType.Prefab, new List<string> { ".prefab" } },
            { AssetFileType.Audio, new List<string> { ".mp3", ".ogg", ".wav" } },
            { AssetFileType.Video, new List<string> { ".mp4" } },
            { AssetFileType.Animation, new List<string> { ".anim" } },
            { AssetFileType.AnimationController, new List<string> { ".controller" } },
			{ AssetFileType.IkMask, new List<string> { ".mask" } },
			{ AssetFileType.Mat, new List<string> { ".mat" } },
            { AssetFileType.Texture, new List<string> { ".png", ".tga", ".bmp", ".jpg", ".dds", ".jpeg", ".psd", ".hdr", ".exr" } },
            { AssetFileType.Atlas, new List<string> { SPRITE_ATLAS_EXT } },
            { AssetFileType.Font, new List<string> { ".ttf" } },
            { AssetFileType.Mesh, new List<string> { ".fbx" } },
            { AssetFileType.SAsset, new List<string> { Const.SASSET_EXTENSION } },
            { AssetFileType.PlayableAsset, new List<string> { ".playable" } },
            { AssetFileType.Shader, new List<string> { ".shader", ".cg", ".cginc", ".shadervariants", ".compute" } },
            { AssetFileType.Lua, new List<string> { ".lua", ".lua.txt", ".lua.bytes" } },
            { AssetFileType.Txt, new List<string> { TEXT_FILE_EXT, ".xml" } },
            { AssetFileType.Json, new List<string> { ".json" } },
            { AssetFileType.Binary, new List<string> { BINARY_FILE_EXT_0, BINARY_FILE_EXT_1, BINARY_FILE_EXT_2 } },
            { AssetFileType.TimelineSignal, new List<string> { ".signal" } },
            { AssetFileType.Scene, new List<string> { SCENE_EXT } },
            { AssetFileType.CsSharp, new List<string> { ".cs" } },
            { AssetFileType.Dll, new List<string> { ".dll", ".so", ".a" } },
            { AssetFileType.Bundle, new List<string> { ".bundle" } }
        };
                
        /// <summary>
        /// 取得指定类型的Asset的文件，所支持的后缀列表
        /// </summary>
        /// <param name="iType">Asset类型</param>
        /// <returns>后缀列表</returns>
        public static List<string> GetAssetExtensions(AssetFileType iType)
        {
            if (!_assetFileExtensions.TryGetValue(iType, out var oExtensions)) return null;
            return oExtensions;
        }
        
        /// <summary>
        /// 取得文件名
        /// </summary>
        /// <param name="iFilePath">文件全路径</param>
        /// <param name="iIncludeExtension">是否包含文件后缀名</param>
        /// <returns>文件名</returns>
        public static string GetFileName(string iFilePath, bool iIncludeExtension = true)
        {
	        if (string.IsNullOrEmpty(iFilePath)) return null;
	        return iIncludeExtension ? Path.GetFileName(iFilePath) : Path.GetFileNameWithoutExtension(iFilePath);
        }
        
        /// <summary>
        /// 取得文件名后缀名
        /// </summary>
        /// <param name="iFilePath">文件路径</param>
        /// <returns>文件名后缀名</returns>
        public static string GetFileExtension(string iFilePath)
        {
            if (string.IsNullOrEmpty(iFilePath))
            {
	            HLogger.Warning("UtilsIo::GetFileExtension():The file path is null or empty!");
                return null;
            }
			
            var fileExtension = Path.GetExtension(iFilePath);
            if (!string.IsNullOrEmpty(fileExtension)) return fileExtension;
			
            HLogger.Warning($"UtilsIo::GetFileExtension():No valid file extension exist!(Path:{iFilePath})");
            return null;

        }		
        
        /// <summary>
        /// 取得文件名
        /// </summary>
        /// <param name="iUrl">iUrl</param>
        /// <returns>文件名</returns>
        public static string GetFileNameByUrl(string iUrl)
        {
	        if (string.IsNullOrEmpty(iUrl)) return null;

	        var index = iUrl.LastIndexOf("/", StringComparison.Ordinal);
	        if (-1 != index)
	        {
		        return iUrl.Substring(index+1);
	        }
	        return iUrl;
        }
        
        private static List<string> _fileExcludes = new List<string>
        {
	        "^.*.meta$", "^.*.mdown$", "^.*.pdf$", ".ds_store", "makefile", "readme"
        };
        
        /// <summary>
		/// 取得所有文件
		/// </summary>
		/// <param name="iDir">指定目录</param>
		/// <param name="iSearchOptions">检索选项</param>
		/// <param name="iSearchPattern">检索模式(例如:(*.exe|*.txt))</param>
		/// <param name="iExcludes">排除列表</param>
		/// <returns>文件列表</returns>
		public static List<string> GetAllFiles(
			string iDir, SearchOption iSearchOptions = SearchOption.AllDirectories, 
			string iSearchPattern = "*.*", List<string> iExcludes = null)
		{
			if (string.IsNullOrEmpty(iDir)) return null;
			if (iDir.Contains("/.svn/") || iDir.Contains("\\.svn\\")) return null;
			if (!Directory.Exists(iDir)) return null;
			var dirInfo = new DirectoryInfo(iDir);
			var targetDir = dirInfo.FullName;

			var filesList = new List<string>();
			string regexExcludes = null;
			try   
			{  
				var searchPattern = iSearchPattern;
				if(string.IsNullOrEmpty(searchPattern)) {
					searchPattern = "*.*";
				}

				var excludes = iExcludes;
				if (null == excludes)
				{
					excludes = _fileExcludes;
				}
				else
				{
					if (0 >= iExcludes.Count)
					{
						excludes.AddRange(_fileExcludes);
					}
				}
				// 去重复
				excludes = excludes.Distinct().ToList();
				
				// 排除用正则表达式
				regexExcludes = $@"{string.Join("|", excludes.ToArray())}";
				var files = Directory.GetFiles(targetDir, searchPattern, iSearchOptions);
				var fCount = files.Length;
				for (var fIdx = 0; fIdx < fCount; fIdx++)
				{
					var file = files[fIdx];
					if(string.IsNullOrEmpty(file)) continue;
					file = VerifyPath(file);
					if(string.IsNullOrEmpty(file)) continue;
					
					// 排除对象
					var fileTmp = file.ToLower();
					if (Regex.IsMatch(fileTmp, regexExcludes)) continue;
					if(fileTmp.Contains("/.svn/") || fileTmp.Contains("\\.svn\\")) continue;
					
					fileTmp = file;
					if (IsAssetPath(fileTmp, out var oAssetPath))
					{
						fileTmp = oAssetPath;
					}
					
					filesList.Add(fileTmp);
				}

			}  
			catch (DirectoryNotFoundException exp)   
			{  
				HLogger.Fatal($"UtilsIo::GetAllFiles():Failed!(Dir:{targetDir} Excludes:{regexExcludes} SearchPattern:{iSearchPattern} Options:{iSearchOptions} detail:{exp.Message})");
			} 

			return filesList;
		}
        
        

        /// <summary>
        /// 取得Asset文件类型
        /// </summary>
        /// <param name="iAssetType">Asset类型</param>
        /// <param name="iDefaultType">默认类型</param>
        /// <returns>Asset文件类型</returns>
        public static AssetFileType GetAssetFileType(Type iAssetType, AssetFileType iDefaultType = AssetFileType.Unknown)
        {
	        var type = AssetFileType.Unknown;
            var targetType = iAssetType;
            if (targetType == typeof(GameObject))
            {
                type = AssetFileType.Prefab;
            }
            else if (targetType == typeof(AudioClip))
            {
                type = AssetFileType.Audio;
            }
            else if (targetType == typeof(VideoClip))
            {
	            type = AssetFileType.Video;
            }
            else if (targetType == typeof(AnimationClip) ||
                     targetType == typeof(Animator))
            {
                type = AssetFileType.Animation;
            }
            else if (targetType == typeof(RuntimeAnimatorController))
            {
	            type = AssetFileType.AnimationController;
            }
            else if (targetType == typeof(SignalAsset))
            {
	            type = AssetFileType.TimelineSignal;
            }
            else if (targetType == typeof(Material))
            {
                type = AssetFileType.Mat;
            }
            else if (targetType == typeof(Texture) ||
                     targetType == typeof(Texture2D))
            {
                type = AssetFileType.Texture;
            }
            else if (targetType == typeof(Sprite))
            {
                type = AssetFileType.Sprite;
            }
            else if (targetType == typeof(SpriteAtlas))
            {
                type = AssetFileType.Atlas;
            }
            else if (targetType == typeof(Font))
            {
                type = AssetFileType.Font;
            }
            else if (targetType == typeof(Shader))
            {
                type = AssetFileType.Shader;
            }
            else if (targetType == typeof(TextAsset))
            {
                type = AssetFileType.Txt;
                if (AssetFileType.Lua == iDefaultType ||
                    AssetFileType.Json == iDefaultType)
                {
	                type = iDefaultType;
                }
            }
            else
            {
                type = iDefaultType;
            }
            return type;
        }

        /// <summary>
        /// 取得Asset文件类型
        /// </summary>
        /// <param name="iDefaultType">默认类型</param>
        /// <typeparam name="T">目标类型</typeparam>
        /// <returns>Asset文件类型</returns>
        public static AssetFileType GetAssetFileType<T>(AssetFileType iDefaultType = AssetFileType.Unknown)
            where T : UnityEngine.Object
        {
	        return GetAssetFileType(typeof(T), iDefaultType);
        }
        
        /// <summary>
        /// 取得Asset文件类型
        /// </summary>
        /// <param name="iAssetKey">Asset Key</param>
        /// <returns>Asset文件类型</returns>
        public static AssetFileType GetAssetFileTypeByAssetKey(string iAssetKey)
        {
	        return GetAssetFileTypeByAssetKey(iAssetKey, out var oFileExtension);
        }

        /// <summary>
        /// 取得Asset文件类型
        /// </summary>
        /// <param name="iAssetKey">Asset Key</param>
        /// <param name="oFileExtension">文件后缀(返回值均为小写)</param>
        /// <returns>Asset文件类型</returns>
        public static AssetFileType GetAssetFileTypeByAssetKey(string iAssetKey, out string oFileExtension)
        {
            var abType = AssetFileType.Unknown;
            oFileExtension = GetFileExtension(iAssetKey);
            if (string.IsNullOrEmpty(oFileExtension))
            {
                var detail = string.IsNullOrEmpty(iAssetKey) ? "null or empty" : iAssetKey;
                HLogger.Warning($"UtilsIo::GetAssetFileTypeByAssetKey():UnKnow file extension!(AssetKey:{detail})");
                return abType;
            }
            var fileExtensionLower = oFileExtension.ToLower();

            var flg = false;
            foreach (var it in _assetFileExtensions)
            {
                if(AssetFileType.Unknown == it.Key) continue;
                var extensions = it.Value;
                if(null == extensions || 0 >= extensions.Count) continue;
                var count = extensions.Count;
                for (var idx = 0; idx < count; idx++)
                {
                    var ext = extensions[idx];
                    if(string.IsNullOrEmpty(ext)) continue;
                    // 不区分大小写，统一用小写比较区分
                    flg = fileExtensionLower.Equals(ext.ToLower());
                    if (flg)
                    {
                        abType = it.Key;
                        break;
                    }
                }
                
                if (flg)
                {
                    break;
                }
            }

            if (AssetFileType.Txt == abType)
            {
	            var luaExt = $".lua{TEXT_FILE_EXT}";
	            if (iAssetKey.EndsWith(luaExt))
	            {
		            abType = AssetFileType.Lua;
	            }
            }
            else if (AssetFileType.Binary == abType)
            {
	            var luaExt1 = $".lua{BINARY_FILE_EXT_0}";
	            var luaExt2 = $".lua{BINARY_FILE_EXT_1}";
	            if (iAssetKey.EndsWith(luaExt1) || iAssetKey.EndsWith(luaExt2))
	            {
		            abType = AssetFileType.Lua;
	            }
            }
            
            if (!flg)
            {
	            HLogger.Error($"UtilsIo::GetAssetFileTypeByAssetKey():Unknown Asset Type!(AssetKey={iAssetKey}))");
            }
            
            return abType;
        }
        
        /// <summary>
        /// 生成App文件名
        /// </summary>
        /// <param name="iPlatform">平台/渠道类型</param>
        /// <param name="iBuildMode">打包模式</param>
        /// <param name="iAppVersion">App版本号</param>
        /// <param name="iBuildTime">打包时间</param>
        /// <param name="iTag">标签(用以设定自定义字段)</param>
        /// <returns>App文件名</returns>
        public static string GenerateAppFileName(
            PlatformType iPlatform, BuildMode iBuildMode, string iAppVersion,
            string iBuildTime, string iTag = null)
        {
            var appFileName = $"{iPlatform}_{iBuildMode}_{iAppVersion}";
            
            if (!string.IsNullOrEmpty(iTag))
            {
                appFileName = $"{appFileName}_{iTag}";
            }
            appFileName = $"{appFileName}_{iBuildTime}";
            
            var fileExtension = ".apk";
            if (PlatformType.IOS == iPlatform)
            {
                fileExtension = ".ipa";
            }
            appFileName = $"{appFileName}{fileExtension}";
            return appFileName;
        }
        
        /// <summary>
        /// 将指定字节数写入文件
        /// </summary>
        /// <param name="iFs">文件写入流</param>
        /// <param name="iBytes">字节数组</param>
        /// <param name="iLength">长度</param>
        /// <param name="iFlush">Flush标志位</param>
        /// <returns>true:成功; false:失败;</returns>
        public static bool WriteToFile(
	        FileStream iFs, byte[] iBytes, int iLength, 
	        bool iFlush = true)
        {
	        if (null == iFs) return false;
	        // 若无内容写入，则返回True
	        if (null == iBytes || 0 >= iBytes.Length) return true;
			
	        // 将内容再写入本地文件中
	        iFs.Write(iBytes, 0, iLength);
	        if (iFlush)
	        {
		        iFs.Flush();
	        }
	        return true;
        }
		
        /// <summary>
        /// 写入文本文件
        /// </summary>
        /// <param name="iFile">文件</param>
        /// <param name="iContent">文本内容</param>
        /// <param name="iExistCheck">存在校验(true:校验(若已有同名文件存在，则删除); false:不校验(即确保调用之前同名文件不存在);)</param>
        /// <returns>true:成功; false:失败;</returns>
        public static bool WriteTxtFile(string iFile, string iContent, bool iExistCheck = false)
        {
	        if (string.IsNullOrEmpty(iFile) || string.IsNullOrEmpty(iContent)) return false;

	        // 文件校验
	        var flg = true;
	        if (iExistCheck && File.Exists(iFile))
	        {
		        flg = DeleteFile(iFile);
	        }
	        if (!flg) return false;
			
	        // 导出JSON文件
	        File.WriteAllText(iFile, iContent);

	        // 返回，文件存在即成功
	        return File.Exists(iFile);
        }

        /// <summary>
        /// 取得文件的大小
        /// <para>* 单位 : Byte</para>
        /// </summary>
        /// <param name="iFilePath">文件路径</param>
        /// <returns>文件的大小(单位:Byte)</returns>
        public static long GetFileSize(string iFilePath)
        {
	        if (string.IsNullOrEmpty(iFilePath) || !File.Exists(iFilePath)) return 0L;
	        
	        return (new FileInfo(iFilePath)).Length;
        }
        
        /// <summary>
        /// 移动文件
        /// </summary>
        /// <param name="iSrcFile">源文件</param>
        /// <param name="iDstFile">目标文件</param>
        /// <param name="iForce">强制标志位(true:若目标文件存在，则覆盖; false:若目标文件存在，则返回拷贝成功;)</param>
        /// <returns>true:成功; false:失败;</returns>
        public static bool MoveFile(string iSrcFile, string iDstFile, bool iForce = false)
        {
	        if (string.IsNullOrEmpty(iSrcFile) || !File.Exists(iSrcFile) || string.IsNullOrEmpty(iDstFile)) return false;

	        // 若目标文件已存在
	        if (!iForce && File.Exists(iDstFile)) return true;
			
	        var flg = true;
	        if (iForce)
	        {
		        flg = DeleteFile(iDstFile);
	        }
	        if (!flg) return false;
			
	        // 移动下载文件到加载目录
	        File.Move(iSrcFile, iDstFile);

	        // 文件存在，则移动成功
	        flg = File.Exists(iDstFile);
	        
	        // 若目标文件存在，则移动文件成功
	        return flg;
        }
        
        /// <summary>
        /// 删除文件
        /// </summary>
        /// <param name="iFile">文件</param>
        /// <returns>true:成功; false:失败;</returns>
        public static bool DeleteFile(string iFile)
        {
	        if (string.IsNullOrEmpty(iFile) || !File.Exists(iFile)) return true;
	        File.Delete(iFile);
	        return !File.Exists(iFile);
        }

#endregion
        
    }
}
